Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC 185: Add WebDriver BiDi support to testdriver.js #185

Merged
merged 16 commits into from
Apr 23, 2024

Conversation

sadym-chromium
Copy link
Contributor

@sadym-chromium sadym-chromium commented Mar 12, 2024

Re-apply #182

Add “testdriver.js” support for WebDriver BiDi events and actions.


Preview

@sadym-chromium sadym-chromium marked this pull request as ready for review March 12, 2024 11:25
@sadym-chromium sadym-chromium changed the title Reapply "RFC 182: Add WebDriver BiDi support to testdriver.js (#182)"… RFC 185: Add WebDriver BiDi support to testdriver.js Mar 12, 2024
@sadym-chromium sadym-chromium force-pushed the sadym/testdriver-bidi branch 2 times, most recently from 2581249 to 9ded53c Compare March 13, 2024 10:51
Copy link
Member

@foolip foolip left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've reviewed this in the form of a document before. Spotted one minor thing when skimming again.

rfcs/testdriver_bidi.md Outdated Show resolved Hide resolved
@jgraham
Copy link
Contributor

jgraham commented Mar 13, 2024

As discussed in the infra meeting I'm going to review this, but it may take some time since it's a rather substantial proposal that is likely to have significant knock-on effects. It would also be useful to get review from anyone who is not planning to use the webdriver backend to wptrunner in the near future; that might include WebKit (e.g. @gsnedders) and Servo (@mrobinson perhaps).

@OrKoN
Copy link
Contributor

OrKoN commented Apr 2, 2024

Friendly ping to review the RFC @jgraham @gsnedders. There is a meeting tonight that offers an opportunity to discuss the proposed changes if there are any questions.

@jgraham
Copy link
Contributor

jgraham commented Apr 3, 2024

Just FYI, I have started looking at this. I'm not sure if it's making the right set of tradeoffs in the current form, but I need more time to look again.

Copy link
Contributor

@jgraham jgraham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the long delay in providing concrete feedback here.

I'm generally very worried about the potential to leak state between tests. I think the harness needs to take responsibility for managing at least event subscription state rather than depending on developers to correctly clean up after every test.

I'm also worried about the fact that we seem to be keeping some of the hacks that were only really required because of limitations of WebDriver. In particular keeping the ActionsContext for BiDi actions seems likely to cause problems.

I'd also like to see examples of how we could use the API for use cases that currently aren't possible or are prohibitively difficult, for example running actions in cross-origin windows. BiDi should unlock that, since it adds the ability to message arbitrary contexts. However the focus on just getting console.log events doesn't really reveal if this design is a useful stepping stone to that outcome. I worry that choices here could impede our ability to make the best use of BiDi in the future, if tests end up relying on accidental features of the design.

const some_message = "SOME MESSAGE";
// Subscribe to `log.entryAdded` BiDi events. This will not add a listener to the page.
await test_driver.bidi.log.entry_added.subscribe();
test.add_cleanup(async function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making each test handle its own subscriptions seems problematic.

Also, if we use BiDi in the harness itself, allowing the test to unsubscribe from events could break the harness (we have some ideas to help with this in general, but that requires BiDi changes).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to address the concern, we need to add a dependency between testharness and testdriver. I see the following ways:

  1. Make testharness aware of the events concept and handle subscriptions there. The events are still implemented in testdriver:
...
promise_test(async (test) => {
    ...
    const log_entry_promise = test.events["log.entryAdded"].once();
    ...
}, "Assert log event is logged", {events: {"log.entryAdded": test_driver.bidi.log.entry_added}});

The test_driver is required to allow testharness managing the subscriptions. The user is supposed to use events via testharness ( test.events...). The specific API can be discussed.
2. Make testdriver aware of testharness:

promise_test(async (test) => {
    ...
    await test_driver.bidi.log.entry_added.subscribe(test);
    ...
}, "Assert log event is logged");

This will not completely address the concern of testharness relying on events.
3. Do not unsubscribe from the events.

I find the option 1 is the preferable. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't really mean each test() in a page, but between top-level test files.

The problem is that if the test file is allowed unfettered access to BiDi event subscriptions we can't isolate it from any subscriptions that the harness itself depends on, and doing something like closing the bidi session after each test would be very high overhead. The most obvious example would be if we wanted to use script.message to drive the harness, but also allow scripts themselves to emit custom messages. But it could also happen that, for example, we wanted the harness to be able to subscribe to console.log messages and re-emit them in the harness logs. Allowing the test to unsubscribe from those events would break that. So probably the harness has to manage subscriptions and know which events it subscribed to, and which should be forwarded back to the browser.

Copy link
Contributor

@OrKoN OrKoN Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be sufficient if we implement automatic clean up of events similar to how BiDi's own test suite does it https://github.com/web-platform-tests/wpt/blob/d7b9fab5017587079985d5ab29781fcf3043533e/webdriver/tests/support/fixtures_bidi.py#L50 ? we could have executewebdriver.py record all subscribe calls coming from the test file and run corresponding unsubscribe calls filtering out events required by the testharness? In that case, unsubscribe calls would not be exposed to testdriver. There will probably be some edge cases around unsubscribing context-specific and global subscriptions at the same time.

Alternative ideas would require changing the spec:

  1. support multiple light-weight BiDi sessions that do not require starting/shutting down the browser.
  2. returning a subscriptionId in subscribe calls so that only specific subscriptions could be cleaned up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the API in option 1 partly addresses the concerns you mentioned. It allows implementing event subscription and filtering in the testharness without exposing that logic to the test file, but the specific filtering of let's say script.message intended to be send to the test or to the testharness can be tricky.

So probably the harness has to manage subscriptions and know which events it subscribed to, and which should be forwarded back to the browser.

Do you mean the backend testharness, or the client-side?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW @OrKoN's option 2 is something we'd like to propose for the BiDi spec (although it might have to be opt-in to require a handle to unsubscribe since that's not how things currently work).

Copy link
Contributor Author

@sadym-chromium sadym-chromium Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The proper long-term solution would be to provide separate sessions for the testharness and for each test files. This would provide proper isolations and tear down.

As an intermediate solution, in the new revision of the example PR I implemented clean up on the wptrunner side as a part of BidiEventsProtocolPart. This approach is used in the WPT BiDi tests (fixtures). This allowed to remove unsubscribe from the testdriver API, but we can always add it, if we have a valid use case.

@jgraham WDYT? If you think this approach makes sense, I will update the RFC accordingly.

Copy link
Contributor Author

@sadym-chromium sadym-chromium Apr 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated the RFC and the example with the new version. The protocol part BidiEventsProtocolPart has a method unsubscribe_all, which is responsible for keeping track of all the subscriptions.


##### Introduce `AsyncCallbackHandler`

Add a `AsyncCallbackHandler`, which is an extension of `CallbackHandler` with support for async actions. It requires an event loop, in which it will schedule the tasks. Async action processing is done in the same way as CallbackHandler is done, but in the dedicated task. This would not allow raising unexpected exceptions in wptrunner. Testdriver will still be notified about those unexpected exceptions. [Example](https://github.com/web-platform-tests/wpt/pull/44649/files#diff-3de439d55203876d452599a1a14968409df69e5869b16918ceec378a6b3345a4R813) implementation.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the PR, I don't like the way this is continuing to use ActionContext.

One of the big advantages of BiDi from the wpt point of view is that it should be possible to communicate with any window, not just the one that currently has WebDriver focus. This proposal seems to end up always switching the WebDriver current browsing context to the one that's getting the BiDi command, which in my opinion rather defeats the point.

Also the use of the ActionContext seems possibly unsound; if you have multiple actions running concurrently I think they can switch the current browsing context out from under each other.

Copy link
Contributor Author

@sadym-chromium sadym-chromium Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the ActionContext is not needed in the AsyncCallbackHandler, as there is no need in switching to the required context. Updated the example.

Copy link
Contributor Author

@sadym-chromium sadym-chromium Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the AsyncCallbackHandler section and example


###### Messages from testdriver to wptrunner

The communication channel between testdriver and wptrunner is set in the `do_testharness` method. Currently the messages from testdriver to wptrunner it is done via long poll by calling BaseProtocolPart.execute_async_script which is synchronous (async in the name indicates that the script is wrapped in a special way before sent to the browser), and blocks processing the event loop in wptrunner, which in turn blocks the event processing while waiting for the next message form testdriver to wptrunner. To overcome this limitation, this RFC changes the wptrunner - testdriver communication channel to asynchronous non-blocking [`BidiScriptProtocolPart.async_call_function`](https://github.com/web-platform-tests/wpt/pull/44649/files#diff-e5a8911dd97e0352b1b26d8ce6ef0a92b25378f7c9e79371a1eb1b1834bc9a8dR761) if available. `WebDriverBidiProtocol`’s event loop should be used for message processing. This behavioral change is done only for protocols supporting BiDi.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like for BiDi we should be looking to not have the resume script at all, but instead have the harness emit custom script.message events to communicate information with the harness. That's a bigger change, but I'd like to at least have a sketch of where we're going here, to be sure we aren't backing ourselves into a corner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it s/with the harness/with the testdriver, or I misread the comment?

... but instead have the harness emit custom script.message events to communicate information with the testdriver...

Do you mean in case of BiDi, the testharness should still make a long poll for getting commands from testdriver, but emit message events directly to window, avoiding __wptrunner_process_next_event? I'm not sure if I'm getting the point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/with the harness/with the browser/ yes.

In general the current way the testdriver event queue works, with __process_next_event is a workaround for limitations in WebDriver classic. I've been assuming that once we have BiDi we'd be able to move away from that model of polling an event queue in a single window, and instead use the browser event loop directly, and communicate via WebDriver BiDi events rather than blocking script evaluation.

We don't necessarily have to do that right away, but I'd at least like to see what the migration path looks like,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean in case of BiDi, the testharness should still make a long poll for getting commands from testdriver, but emit message events directly to window, avoiding __wptrunner_process_next_event? I'm not sure if I'm getting the point.

Oh, I got it. You mean the harness can provide a channel for the client-side wptrunner. I guess it is possible and makes sense, but I'd prefer to consider it in a different RFC to reduce execution risks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you have some time to prototype that I'd appreciate it. As mentioned one concern in general is that making the wrong first step could make it harder in the long run if we bake in assumptions that don't work for other use cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the communication between the testharness and the browser is internal and it is not exposed to test authors, it looks like it can be addressed separately. What assumptions from this RFC could prevent us from changing the event implementation in the future?

It sounds like a preload script + a channel for setting up biderectional communication would not require API changes on the testdriver.js side.

@sadym-chromium sadym-chromium force-pushed the sadym/testdriver-bidi branch 2 times, most recently from 9167f55 to cd3db47 Compare April 11, 2024 12:24
README.md Outdated Show resolved Hide resolved
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
@sadym-chromium
Copy link
Contributor Author

I'd also like to see examples of how we could use the API for use cases that currently aren't possible or are prohibitively difficult, for example running actions in cross-origin windows. BiDi should unlock that, since it adds the ability to message arbitrary contexts. However the focus on just getting console.log events doesn't really reveal if this design is a useful stepping stone to that outcome. I worry that choices here could impede our ability to make the best use of BiDi in the future, if tests end up relying on accidental features of the design.

The core issue with handling several browsing context from a testdriver is in the fact the testdriver is not aware of browsing context ids. I see two approaches to address this:

  1. When BiDi is available, allow testdriver to use window itself instead of the window.__wptrunner_id as a context. On the testdriver side, parse the testdriver messages and extract the browsing context ID.
  2. Make testdriver be aware of it's own browsing context ID. This can be done by one of this ways:
    1. Add to script.addPreloadScript a new argument type browsingContext, which will be resolved to the actual browsingContext ID when called.
    2. Set a promise window.__browsing_context_id via script.addPreloadScript, and resolve it via script.evaluate.

@sadym-chromium
Copy link
Contributor Author

I'd also like to see examples of how we could use the API for use cases that currently aren't possible or are prohibitively difficult, for example running actions in cross-origin windows. BiDi should unlock that, since it adds the ability to message arbitrary contexts. However the focus on just getting console.log events doesn't really reveal if this design is a useful stepping stone to that outcome. I worry that choices here could impede our ability to make the best use of BiDi in the future, if tests end up relying on accidental features of the design.

I updated the example by adding a parameter contexts to subscribe action, which can be either a window proxy or a string representing browsing context id. This became possible because of the _get_next_message uses BiDi when it is available.

@sadym-chromium
Copy link
Contributor Author

@jgraham do you think we need to extend other test in the example to make a more explicit use case of BiDi?

rfcs/testdriver_bidi.md Outdated Show resolved Hide resolved
rfcs/testdriver_bidi.md Outdated Show resolved Hide resolved
rfcs/testdriver_bidi.md Outdated Show resolved Hide resolved
sadym-chromium and others added 3 commits April 16, 2024 08:18
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
@sadym-chromium
Copy link
Contributor Author

sadym-chromium commented Apr 16, 2024

I'd also like to see examples of how we could use the API for use cases that currently aren't possible or are prohibitively difficult, for example running actions in cross-origin windows. BiDi should unlock that, since it adds the ability to message arbitrary contexts. However the focus on just getting console.log events doesn't really reveal if this design is a useful stepping stone to that outcome. I worry that choices here could impede our ability to make the best use of BiDi in the future, if tests end up relying on accidental features of the design.

Here is a draft example of what it can look like: sadym-chromium/wpt#450.
@jgraham @gsnedders WDYT?

rfcs/testdriver_bidi.md Outdated Show resolved Hide resolved
Copy link
Contributor

@jgraham jgraham left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for making the changes!

Generally I think this looks OK to me now. Ideally I'd like some feedback from someone that doesn't yet have a BiDi implementation they could hook up to this, in case there are additional concerns from that point of view.

@foolip
Copy link
Member

foolip commented Apr 23, 2024

I'll go ahead and merge this now. The RFC has been open for over a month and it's fair to say that we've ensured "that all participants have time to consider it." If anyone from Apple wants to leave feedback on the design, it's not too late for that even after implementation has begun.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants